; 功能：开启分段和分页测试
;   进入保护模式之后，线性地址空间已经能达到4GB
;   我们使用的线性地址从0x10000开始
;   另外要注意一点，页目录表和页表的线性地址要与物理地址一致
;   因此我们这里做多个个4K页的映射，进入保护模式的代码和页目录表以及页表的映射，其线性地址和物理地址一致
;       另外添加一个专门的页映射到10M以后的物理地址，作为验证使用   
;   0-12M以下的线性地址映射到相同的物理地址空间
;   12M-16M的线性地址映射到20M-24M的物理地址空间
;   其他线性地址空间暂不映射

; 0 - 12M 线性地址空间的内容包括：特权级为0的数据段和代码段，范围是0-12M，显存段；主要是做初始化页表，进入保护模式
; 12 - 16M 线性地址空间的内容包括：特权级为3的数据段和代码段， 范围是12 - 16M，并调用特权级为0的显示字母接口，然后回到特权级为3

; 先进入保护模式，设置好页表
; 在特权级0下，打印字符A，并将字符B写入到15M处
; 在特权级0下，将调用打印15M地址处的代码复制到12M地址空间开始处
; 从特权级0下跳转到特权级3，特权级3中通过调用门，调用特权级0的代码，特权级0的代码为打印数据
; 调用完成后，回到特权级3

%include "pm.inc"

PageDirBase     equ     100000h             ; 页目录表基地址，1M处，4K对齐
PageTableBase   equ     100000h + 1000h     ; 页表基地址，1M+4K处（1M~1M+4K之间为页表目录使用）
SegBaseCpl0     equ     10000h              ; 特权级0的段基址，从64K处开始
SegLimitCpl0    equ     0C00000h - 010000h  ; 特权级0的段长度：12M-4K
SegBaseCpl3     equ     0C00000h            ; 特权级3的段基址：从12M开始
SegLimitCpl3    equ     0400000h            ; 特权级3的段长度：4M
SegVideoBase    equ     0B8000h             ; 显存段基址
TopOfStackCpl3  equ     0A000h              ; 特权级3的堆栈底  

jmp LABEL_BEGIN
;===============================================================================
; 页目录表和页表，需要4K对齐
; 页目录表，实际只使用了前面4项，共占用4 * 1024 * 4K = 16M的物理页分配
PAGE_DIRECTORY_TABLE:
    ;times 0FFFh dd 0    ; 1024 * 4字节 = 4K
    PageEntry   0,      PA_P + PA_RW                ; 对应第1个页表，线性地址为0-4Mb，映射到物理地址0-4Mb
    dd 0                                ; 第2个页表，暂不映射。线性地址4M - 8M
    dd 0                                ; 第3个页表，暂不映射。线性地址8M -12M
    PageEntry   0,      PA_P + PA_RW + PA_USER      ; 对应第4个页表，线性地址为12-16M，映射到物理地址20-24M页，且它是特权级3下的页面
    ; 后面页目录暂时不处理了，测试程序不会用
    times 1020 dd 0

PAGE_TABLES:
PAGE_TABLE1:    ; 第1个页表，最多映射到1024个页，每页4K，线性地址0-3FFFFFh，物理地址0-3FFFFFh
    times 0FFFh db 0
PAGE_TABLE4:    ; 第4个页表，最多映射到1024个页，每页4K，线性地址0C00000h-0FFFFFFh，物理地址01400000h-017FFFFFh
    times 0FFFh db 0

;===============================================================================

[SECTION .gdt]
; GDT描述符
; 每一项的格式: 
;   数据段/代码段描述符：       段基址， 段界限，   属性
;   门描述符：                段偏移， 段选择符，  属性,    参数个数
LABEL_GDT:                  Descriptor      0,              0,                        0                               ; 空描述符
LABEL_DESC_CODE32_DPL0:     Descriptor      SegBaseCpl0,        SegLimitCpl0 - 1,     DA_C + DA_32 + DA_DPL0          ; 特权级0的代码段
LABEL_DESC_DATA_DPL0:       Descriptor      SegBaseCpl0,        SegLimitCpl0 - 1,     DA_DRW + DA_DPL0                ; 特权级0的数据段
LABEL_DESC_VIDEO_DPL0:      Descriptor      SegVideoBase,            0ffffh,          DA_DRW + DA_DPL0                ; 特权级0的显存地址段（可读可写数据段）
LABEL_DESC_STACK_DPL0:      Descriptor      SegBaseCpl0,        TopOfStackCpl0,       DA_DRWA + DA_32 + DA_DPL0       ; 特权级0的堆栈段                  

LABEL_DESC_CODE32_DPL3:     Descriptor      SegBaseCpl3,       SegLimitCpl3 - 1,               DA_C + DA_32 + DA_DPL3          ; 特权级3的代码段 12 - 16M
LABEL_DESC_DATA_DPL3:       Descriptor      SegBaseCpl3,       SegLimitCpl3 - 1,               DA_DRW + DA_DPL3                ; 特权级3的数据段 12 - 16M
;LABEL_DESC_VIDEO_DPL3:      Descriptor      SegVideoBase,        0ffffh,                 DA_DRW + DA_DPL3                   ; 特权级3的显存地址段（可读可写数据段）
LABEL_DESC_STACK_DPL3:      Descriptor      SegBaseCpl3,       TopOfStackCpl3 - 1,               DA_DRWA + DA_32 + DA_DPL3       ; 特权级3的堆栈段, 12 + 40K处

LABEL_DESC_CALL_GATE:       GateDesc        0, SelectorCode32NewCpl0,      DA_386CGate + DA_DPL3,    0 ; 调用门描述符:32位调用门
LABEL_DESC_TSS:             Descriptor      SegBaseCpl0,              TssLen - 1,                 DA_386TSS                   ; 32位TSS段描述符

; 增加两个数据段，分别指向页目录和页表
LABEL_DESC_PDE:             Descriptor      PageDirBase,        01000h - 1,                  DA_DRW + DA_DPL0                ; 页目录表描述符，4K长度
LABEL_DESC_PTE:             Descriptor      PageTableBase,      0400000h - 1,                DA_DRW + DA_DPL0                ; 页表描述符，4M长度


LABEL_DESC_CODE32_NEW_DPL0: Descriptor      0,    SegCode32CPlPrintLen - 1,    DA_C + DA_32 + DA_DPL0      ; 通过调用门跳转到特权级0的代码段（非一致代码段）

LABEL_DESC_CODE32_CPL3_IN_SEG_4M: Descriptor      0,    SegCodeCallPrintInCpl3Len - 1,    DA_C + DA_32 + DA_DPL3      ; 通过调用门跳转到特权级0的代码段（非一致代码段）
LABEL_DESC_STACK_DPL3_IN_SEG_4M:  Descriptor      0,    TopOfStackCpl3_In4M,   DA_DRWA + DA_32 + DA_DPL3       ; 特权级3的堆栈段, 12 + 40K处


; GDT结束

GdtLen equ $ - LABEL_GDT    ; GDT长度
GdtPtr dw GdtLen - 1        ; GDT界限
       dd 0                 ; GDT基地址


; GDT段选择符
SelectorCode32Cpl0          equ     LABEL_DESC_CODE32_DPL0 - LABEL_GDT
SelectorDataCpl0            equ     LABEL_DESC_DATA_DPL0 - LABEL_GDT
SelectorVideoCpl0           equ     LABEL_DESC_VIDEO_DPL0 - LABEL_GDT
SelectorStackCpl0           equ     LABEL_DESC_STACK_DPL0 - LABEL_GDT
SelectorCode32Cpl3          equ     LABEL_DESC_CODE32_DPL3 - LABEL_GDT + SA_DPL3
SelectorDataCpl3            equ     LABEL_DESC_DATA_DPL3 - LABEL_GDT + SA_DPL3
;SelectorVideoCpl3           equ     LABEL_DESC_VIDEO_DPL3 - LABEL_GDT + SA_DPL3
SelectorStackCpl3           equ     LABEL_DESC_STACK_DPL3 - LABEL_GDT + SA_DPL3
;SelectorCode32NewCpl0       equ     LABEL_DESC_CODE32_NEW_DPL0 - LABEL_GDT
SelectorCodeGate32          equ     LABEL_DESC_CALL_GATE - LABEL_GDT + SA_DPL3
SelectorTss                 equ     LABEL_DESC_TSS - LABEL_GDT
SelectorPde                 equ     LABEL_DESC_PDE - LABEL_GDT
SelectorPte                 equ     LABEL_DESC_PTE - LABEL_GDT
SelectorCode32NewCpl0       equ     LABEL_DESC_CODE32_NEW_DPL0 - LABEL_GDT

SelectorCode32NewCpl3_In4M       equ     LABEL_DESC_CODE32_CPL3_IN_SEG_4M - LABEL_GDT + SA_DPL3
SelectorCode32NewStackCpl3_In4M  equ     LABEL_DESC_STACK_DPL3_IN_SEG_4M - LABEL_GDT + SA_DPL3

; End of GDT SECTION


[SECTION .s16]
[BITS 16]
LABEL_BEGIN:
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0100h ; 初始化各个段

    ; 在进入保护模式之前
    ; 完成段描述符的初始化

    ; 门描述符的初始化，初始化偏移
    ;mov eax, LABEL_SEG_CODE32_CPL0_PRINT
    ;mov word [LABEL_DESC_CALL_GATE], ax
    ;shr eax, 16
    ;mov word [LABEL_DESC_CALL_GATE + 6], ax

    ; TSS段
    xor eax, eax
    mov ax, cs
    shl eax, 4
    add eax, LABEL_TSS
    mov word [LABEL_DESC_TSS + 2], ax
    shr eax, 16
    mov byte [LABEL_DESC_TSS + 4], al
    mov byte [LABEL_DESC_TSS + 7], ah


    xor eax, eax
    mov ax, cs
    shl eax, 4
    add eax, LABEL_SEG_CODE32_CPL0_PRINT
    mov word [LABEL_DESC_CODE32_NEW_DPL0 + 2], ax
    shr eax, 16
    mov byte [LABEL_DESC_CODE32_NEW_DPL0 + 4], al
    mov byte [LABEL_DESC_CODE32_NEW_DPL0 + 7], ah


    ; 为加载GDTR做准备
    xor eax, eax
    mov ax, ds
    shl eax, 4
    add eax, LABEL_GDT  ; gdt基地址 ---> eax
    mov dword [GdtPtr + 2], eax ; gdt基地址 ---> [GdtPtr + 2]

    ; 加载GDTR
    lgdt [GdtPtr]

    ; 关中断 保护模式下的中断处理机制和实模式下不一样，先简单关中断处理
    cli

    ; 打开地址线A20，历史原因，打开它才能访问超过1M的地址空间
    in al, 92h
    or al, 00000010b
    out 92h, al

    ; 准备切换到保护模式
    ; 开启分段
    mov eax, cr0
    or eax, 1       ; CR0的最低位是PE，该值为1时表示启动分段保护模式，开启PE=1模式后，必须立马使用跳转命令，例如下面的jmp命令
    mov cr0, eax

    jmp dword SelectorCode32Cpl0:LABEL_SEG_CODE32_CPL0

; End of [SECTION .s16]

[SECTION .code32]
[BITS 32]
LABEL_SEG_CODE32_CPL0:
    mov ax, SelectorVideoCpl0
    mov gs, ax  ; 视频段选择子

    ; 打印一个字符0
    mov edi, (80 * 11 + 0) * 2     ; 屏幕第11行，第79列
    mov ah, 0ch     ; 0000 黑底， 000C 红字
    mov al, '0'
    mov [gs:edi], ax

    ; 加载堆栈段选择符
    mov ax, SelectorStackCpl0
    mov ss, ax

    ; 加载TSS
    ;mov ax, SelectorTss
    ;ltr ax

    ; 初始化页表目录
    ; 先将页目录的段选择符加载到es
    mov ax, SelectorPde
    mov es, ax
    
    ; 初始化第1个页目录项
    ; 页目录的第1项对应第1个页表，页目录的第1项位于物理地址PageDirBase，第1个页表位于物理地址PageTableBase；
    ; 页目录项的值为 基地址 + 属性 = PageTableBase << 12 + PA_P + PA_RW
    xor edi, edi
    mov eax, PageTableBase
    add eax, PA_P + PA_RW
    stosd

    ; 第2项和第3项为0
    xor eax, eax
    stosd
    stosd
    
    ; 初始化第4个页目录项
    ; 页目录的第4项对应第2个页表，页目录的第4项位于物理地址PageDirBase + 16，第4个页表位于物理地址PageTableBase + 01000h处
    ; 页目录项的值为 基地址 + 属性 = (PageTableBase + 01000h) << 12 + PA_P + PA_RW + PA_USER
    mov eax, PageTableBase + 01000h
    add eax, PA_P + PA_RW + PA_USER
    stosd

    ; 接下来开始初始化页表1和页表4，先加载页表的段选择符
    mov ax, SelectorPte
    mov es, ax

    ; 页表1的物理地址为PageTableBase，它映射的线性地址空间是0-4M，对应的物理地址空间是0-4M
    xor eax, eax
    mov eax, 0 + PA_P + PA_RW
    mov ecx, 1024
    xor edi, edi
.loop_init_pt1:
    stosd
    add eax, 01000h     ; 页的基址每次递增为4K，刚好是1个页的长度
    loop .loop_init_pt1

    ; 页表4的物理地址为PageTableBase + 01000h，它映射的线性地址空间是12-16M，对应的物理地址空间是20-24M
    ; 页表1初始化完成后，edi会自增，刚好指向页表4存放的地址处
    mov ecx, 1024
    xor eax, eax
    mov eax, 01400000h + PA_P + PA_RW + PA_USER             ; 注意和页表1的有两处区别：区别1是基地址是从20M(01400000h)的物理地址空间开始，区别2是属性是普通用户(PA_USER)
.loop_init_pt4:  
    stosd
    add eax, 01000h     ; 页的基址每次递增为4K，刚好是1个页的长度
    loop .loop_init_pt4

    ; 已经初始化完成，那么现在开启分页
    mov eax, PageDirBase            ; 页目录基地址
    ;shl eax, 12
    mov cr3, eax                    ; 将页目录表基地址(物理地址)存起来
    mov eax, cr0
    or  eax, 80000000h              ; 开启分页，PG=1
    mov cr0, eax

    ; 从现在开始分页机制已经生效

    ; 将代码复制到特权级3的起始处：12M
    ; 加载特权级0的数据段选择符
    mov ax, SelectorDataCpl0
    mov ds, ax

    mov ecx, SegCodeCallPrintInCpl3Len
    push es
    push edi
    
    mov ax, SelectorDataCpl3
    mov es, ax
    xor esi, esi
    mov edi, LABEL_SEG_CALL_PRINT_IN_CPL3
.copy_code_to_dest:
    mov al, [ds:edi]
    mov [es:esi], al
    inc edi
    inc esi
    dec ecx
    cmp ecx, 0
    je .finish_copy_code
    jmp .copy_code_to_dest
.finish_copy_code:
    pop edi
    pop es

    ; 使用retf返回切换到特权级3
    xor eax, eax
    mov ax, SelectorStackCpl0
    mov ss, ax
    mov esp, TopOfStackCpl0

    ; 加载TSS
    mov ax, SelectorTss
    ltr ax

    push SelectorStackCpl3        ; 特权级3的堆栈ss
    push TopOfStackCpl3           ; 特权级3的esp
    ; 0个参数，此处无需要处理
    push SelectorCode32Cpl3       ; 特权级3的代码段选择符
    push 0                        ; 特权级代码段的偏移地址，实际就是入口地址，因为段选择符的基址已经是从入口地址处开始，所以偏移从0开始
    retf
  

SegCode32CPl0Len equ $ - LABEL_SEG_CODE32_CPL0
; End of SECTION .code32 cpl0

; 特权级0的堆栈段
[SECTION .data0]
ALIGN 32
[BITS 32]
LABEL_STACK_CPL0:
    times 512 db 0
TopOfStackCpl0 equ $ - LABEL_STACK_CPL0 - 1
; End of SECTION .data0


; 特权级3的堆栈段
[SECTION .data3]
ALIGN 32
[BITS 32]
LABEL_STACK_CPL3:
    times 512 db 0
TopOfStackCpl3_In4M equ $ - LABEL_STACK_CPL3 - 1
; End of SECTION .data3


;=====================================================
; 特权级0：打印指定数目的字符
; 入参：
;   ax 指向待打印字符的段
;   ecx 表示需要打印的字符个数   
;   edx=(dh, dl)=(行位置,列位置)
[SECTION .code32]
[BITS 32]
LABEL_SEG_CODE32_CPL0_PRINT:
    push es
    ;mov es, ax
    ;mov es, 0C00000h ; 暂时先固定
    mov ax, SelectorDataCpl3
    mov es, ax

    mov ecx, 16
    mov dh, 20
    mov dl, 0

    mov ax, SelectorVideoCpl0
    mov gs, ax  ; 视频段选择子

    mov edi, (80 * 20 + 0) * 2     ; 屏幕第dh行，第dl列
    mov ah, 0ch     ; 0000 黑底， 000C 红字
    xor esi, esi
    mov esi, 010000h

    mov al, 'A' ; to do delete
.loop:
    cmp ecx, 0
    je .finish
    ;mov al, [es:esi]
    mov [gs:edi], ax
    inc esi
    dec ecx
    add edi, 2
    inc al ; to do delete
    jmp .loop
.finish:
    pop es
    retf

SegCode32CPlPrintLen equ $ - LABEL_SEG_CODE32_CPL0_PRINT
; End of SECTION .code32 cpl print


; TSS段
[SECTION .dataTss]
ALIGN 32
[BITS 32]
LABEL_TSS:
    dd 0
    dd TopOfStackCpl0       ; 特权级0的堆栈esp
    dd SelectorStackCpl0    ; 特权级0的堆栈ss选择符
    times 22 dd 0
    dw 0
    dw $ - LABEL_TSS + 2    ; I/O位图基址
    db 0FFh                 ; I/O位图结束标志

TssLen equ $ - LABEL_TSS
; End of SECTION .dataTss

; 待拷贝到特权级3的起始地址（线性地址为12M，映射到物理地址16M）处开始执行的代码
LABEL_SEG_CALL_PRINT_IN_CPL3:
    call SelectorCodeGate32:0
    jmp $

    mov ax, SelectorDataCpl3
    mov ds, ax
    ;mov ax, SelectorStackCpl3      ; 从特权级0通过retf跳入到特权级3时候已经在特权级0的堆栈中写入了特权级3的堆栈信息
    ;mov ss, ax
    ;mov esp, TopOfStackCpl3

    ; 只是通过调用门调用特权级0的打印
    ; 往数据段010000h偏移处，写入16个字符
    push edi
    mov edi, 010000h
    mov eax, 'A'
    mov ecx, 16
.set_char:    
    mov [ds:edi], eax
    inc edi
    inc eax
    dec ecx
    cmp ecx, 0
    je .finish_copy
    jmp .set_char

.finish_copy:
    pop edi

    call SelectorCodeGate32:0

    jmp $
SegCodeCallPrintInCpl3Len equ $ - LABEL_SEG_CALL_PRINT_IN_CPL3

